package physics;

import globals.GlobalObjects;

import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collection;

import org.eclipse.swt.graphics.ImageData;

import physics.MovementControl.PlanetMovementController;

import common.Math3D;
import common.Point3D;
import common.Position3D;
import common.SphereMap;



public class Planet extends GameObject {
	
	double heightFactor = 0.01;
	boolean withHeightMap = false;;
	boolean withPathsMap = false;
	SphereMap heightMap = null;
	SphereMap pathsMap = null;
	
	double gravity = 0.1;
	String sunName = null;
	
	private ArrayList<GameObject> objectsOnPlanet = new ArrayList<GameObject>();
	private Sun sun;
	
	
	public Planet(){
		this.setMovementControler(new PlanetMovementController(this));
		this.getMovementControler().rotationY(0.0015);
	}
	
	public void clockTicked(ActionEvent arg0) {
		this.getMovementControler().play();
	}
	
	public void addObjectToPlanet(GameObject object){
		if (!objectsOnPlanet.contains(object))
			objectsOnPlanet.add(object);
	}
	public void removeObjectFromPlanet(GameObject object){
		objectsOnPlanet.remove(object);
	}
	public Collection<GameObject> getObjectsOnPlanet(){
		return objectsOnPlanet;
	}
	public void setParameter(String name, String args[])
	{
		
		if (name.equals("heightImage")) {
			heightMap = new SphereMap(new ImageData(args[0]));
			withHeightMap = true;
		}

		if (name.equals("pathsImage")) {
			pathsMap = new SphereMap(new ImageData(args[0]));
			withPathsMap = true;
		}
		
		if (name.equals("heightFactor")) {
			setHeightFactor(Float.parseFloat(args[0]));	
		}
		
		if (name.equals("radius")) {
			this.setRadius(Double.parseDouble(args[0])); 	
		}
		
		if (name.equals("sun")){
			this.sun = (Sun) GlobalObjects.getGameObject(args[0]);
		}
		
		super.setParameter(name, args);
		
	}

	public void walkOnPlanet(GameObject object, Point3D movementVector){
		
		calculatePosition(object.getCenter(), movementVector);
		calculateFacingVectors(object);
	}
	public void pinpointToPlanet(GameObject object){
		
		Point3D objectCenter = object.getCenter();

		// change to planet coordinates
		objectCenter.sub(position.getCenter());
		objectCenter.rotateAgainst(this.getLeft(),
				this.getUp(), this.getFace());

		// pinpoint
		double distance = surfaceHeight_rotated(objectCenter) + object.getRadius();
		objectCenter.normalize(distance);
		
		// change to world coordinates
		objectCenter.rotateWith(this.getLeft(), 
				this.getUp(), this.getFace());
		objectCenter.add(position.getCenter());
	
		calculateFacingVectors(object);
	}
	
	
	private double surfaceHeight_rotated(Point3D pmz){
		return radius + heightFactor * (heightMap.sampleBilinear(pmz));
	}
	
	public double surfaceDistance(GameObject object){
		Point3D center = Math3D.subPoint(object.getCenter(),
				this.position.getCenter());
		double centerdistance = center.norm();
		
		center.rotateAgainst(this.getLeft(),
				this.getUp(), this.getFace());

		return centerdistance - surfaceHeight_rotated(center);
	}
	

	public double getHeightFactor() {
		return heightFactor;
	}
	public void setHeightFactor(double heightFactor) {
		this.heightFactor = heightFactor;
	}
	public SphereMap getHeightMap() {
		return heightMap;
	}
	public void setHeightMap(SphereMap heightMap) {
		this.heightMap = heightMap;
	}
	public boolean isWithHeightMap() {
		return withHeightMap;
	}

	public void setWithHeightMap(boolean withHeightMap) {
		this.withHeightMap = withHeightMap;
	}
 
	public void setPathsMap(SphereMap pathsMap) {
		this.pathsMap = pathsMap;
	}
	public double getGravity() {
		return gravity;
	}

	public void setGravity(double gravity) {
		this.gravity = gravity;
	}

	public Sun getSun() {
		return sun;
	}

	public void setSun(Sun sun) {
		this.sun = sun;
	}

	public void calculateFacingVectors(GameObject phObj){
		Position3D obj = phObj.getPosition();
		Point3D normal = Math3D.subPoint(obj.getCenter(), position.getCenter());
		normal.normalize();

		if (Math3D.getDotProduct(normal, obj.getFace()) == 1)
			obj.getFace().copy(obj.getUp());
		obj.getUp().copy(normal);
		
		normal.copy(obj.getUp());
		normal.scale(Math3D.getDotProduct(normal, obj.getFace()));
		obj.getFace().sub(normal);
		obj.getFace().normalize();
		
		obj.getLeft().copy(Math3D.getXProduct(obj.getFace(),obj.getUp()));
	}
	private void calculatePosition(Point3D object, Point3D movmentVector) {
		
		Point3D rsource = object.clone();
		rsource.sub(position.getCenter());
		rsource.rotateAgainst(this.getLeft(), 
				this.getUp(), this.getFace());

		Point3D rtarget = Math3D.addPoint(object, movmentVector);
		rtarget.sub(position.getCenter());
		rtarget.rotateAgainst(this.getLeft(),
				this.getUp(), this.getFace());
		
		double distance = surfaceHeight_rotated(rtarget);
		rtarget.normalize(distance);

		Point3D rmovment = Math3D.subPoint(rtarget, rsource);
		rmovment.normalize(movmentVector.norm());
		rtarget = Math3D.addPoint(rsource, rmovment);

		distance = surfaceHeight_rotated(rtarget);
		rtarget.normalize(distance);
		
		if (withPathsMap && !pathsMap.booleanSample(rtarget))
				return;
		
		rtarget.rotateWith(this.getLeft(), 
				this.getUp(), this.getFace());
		rtarget.add(position.getCenter());
		object.copy(rtarget);
	}

}
